﻿//-----------------------------------------------------------------------
// <copyright file="MLScriptRunner.cs" company="MAPILab Ltd">
//     Copyright (c) MAPILab Ltd.  All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
namespace MAPILab.SharePoint.Explorer.Utilities.ScriptRunner
{
    using System;
    using System.CodeDom.Compiler;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Reflection;
    using System.Text.RegularExpressions;
    using MAPILab.SharePoint.Explorer.CodeForm;

    /// <summary>
    /// Contains methods for code run and compile
    /// </summary>
    class MLScriptRunner
    {
        #region Methods

        /// <summary>
        /// Method that compiles code on selected code language
        /// </summary>
        /// <param name="code">the code</param>
        /// <param name="referencedAssemblies">all referances</param>
        /// <param name="codeLanguage">code language</param>
        /// <returns>assembly, errors and warnings collections</returns>
        static public MLCompilationResults Compilation(string code, StringCollection referencedAssemblies, CodeLanguage codeLanguage)
        {
            MLCompilationResults value = null;
            Assembly assembly = null;
            StringCollection errorsCollection = new StringCollection();
            StringCollection warningsCollection = new StringCollection();

            try
            {
                string checkForEmpty = System.Text.RegularExpressions.Regex.Replace(code, "[\n ]", "");

                if ((checkForEmpty != null) && (checkForEmpty.Length > 0))
                {
                    //Create an instance whichever code provider that is needed
                    CodeDomProvider codeProvider = MLUtils.GetCodeProvider(codeLanguage);

                    //add compilation parameters
                    CompilerParameters compilerParams = SetCompilationParameters();
                    
                    //add compilation references
                    SetCompilationReferences(code, referencedAssemblies, codeLanguage, compilerParams);

                    //actually compile the code
                    CompilerResults compilerResults = codeProvider.CompileAssemblyFromSource(compilerParams,code);

                    //Check errors
                    foreach (CompilerError compilerError in compilerResults.Errors)
                    {
                        if (!compilerError.IsWarning)
                        {
                            errorsCollection.Add(compilerError.ErrorText + " (Line: " + compilerError.Line.ToString() + ", Column: " + compilerError.Column.ToString() + ")");
                        }
                        else
                        {
                            warningsCollection.Add(compilerError.ErrorText + " (Line: " + compilerError.Line.ToString() + ", Column: " + compilerError.Column.ToString() + ")");
                        }
                    }

                    //Get assembly
                    if (errorsCollection.Count == 0)
                    {
                        try
                        {
                            assembly = compilerResults.CompiledAssembly;
                        }
                        catch (Exception ex)
                        {
                            errorsCollection.Add(ex.Message);
                        }
                    }
                }
                else
                {
                    errorsCollection.Add("There is no code to compile");
                }

                value = new MLCompilationResults(assembly, errorsCollection, warningsCollection);
            }
            catch (Exception ex)
            {
                throw ex;
            }

            return value;
        }

        /// <summary>
        /// Set compilation references
        /// </summary>
        /// <param name="code">source code</param>
        /// <param name="referencedAssemblies">source code references</param>
        /// <param name="codeLanguage">code language</param>
        /// <param name="compilerParams">compilation parameters</param>
        private static void SetCompilationReferences(string code, StringCollection referencedAssemblies, CodeLanguage codeLanguage, CompilerParameters compilerParams)
        {
            if (referencedAssemblies != null)
                foreach (string refName in referencedAssemblies)
                    compilerParams.ReferencedAssemblies.Add(refName);
            else
            {
                string mask = String.Empty;
                if (codeLanguage == CodeLanguage.VBNet)
                    mask = @"'addref\<(?<addref>[^\>]+)\>";
                else
                    mask = @"///addref\<(?<addref>[^\>]+)\>";

                Regex regex = new Regex(mask);
                MatchCollection refMatches = regex.Matches(code);

                foreach (Match refMatch in refMatches)
                {
                    string refName = refMatch.Groups["addref"].Value;
                    if (!string.IsNullOrEmpty(refName) && !compilerParams.ReferencedAssemblies.Contains(refName))
                    {
                        compilerParams.ReferencedAssemblies.Add(refName);
                    }
                }
            }
        }

        /// <summary>
        /// Set compilation parameters
        /// </summary>
        /// <returns>compilation parameters</returns>
        private static CompilerParameters SetCompilationParameters()
        {
            CompilerParameters compilerParams = new CompilerParameters();
            compilerParams.CompilerOptions = "/target:library /nologo"; // you can add /optimize
            compilerParams.GenerateExecutable = false;
            compilerParams.GenerateInMemory = true;
            compilerParams.IncludeDebugInformation = true;
            return compilerParams;
        }

        /// <summary>
        /// Method that run code and return MLBrowser object
        /// </summary>
        /// <param name="code">the code</param>
        /// <param name="referencedAssemblies">all referances</param>
        /// <param name="mainParams">parameters to Invoke to Main method</param>
        /// <param name="codeLanguage">code language</param>
        /// <returns>object(s), text of object browser window, display mode for browser, errors and warnings collections</returns>
        static public MLExecutionResults Execute(string code, StringCollection referencedAssemblies, List<object> mainParams, CodeLanguage codeLanguage)
        {
            MLExecutionResults executionResults = null;
            MLBrowser browser = null;
            try
            {
                //Compile source code
                MLCompilationResults compilationResults = Compilation(code, referencedAssemblies, codeLanguage);

                if (compilationResults.ErrorsCollection.Count == 0)
                {
                    Module[] mods = compilationResults.Assembly.GetModules(false);
                    Type[] types = mods[0].GetTypes();

                    foreach (Type type in types)
                    {
                        MethodInfo mi = type.GetMethod("Main", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
                        if (mi != null)
                        {
                            //Get parameters
                            object[] parameters = GetParameters(mainParams, mi);

                            //Invoke parameters to method
                            mi.Invoke(null, parameters);

                            //Get MLBrowser object
                            foreach (object o in parameters)
                            {
                                if (o is MLBrowser)
                                {
                                    browser = o as MLBrowser;
                                    break;
                                }
                            }
                            break;
                        }
                    }
                }

                executionResults = new MLExecutionResults(browser, compilationResults.ErrorsCollection, compilationResults.WarningsCollection);

            }
            catch (Exception ex)
            {
                throw ex;
            }

            return executionResults;
        }

        /// <summary>
        /// Get parameters to invoke, if ther is no parameters in main parameters, 
        /// then create new MLBrowser and MCodeForm
        /// </summary>
        /// <param name="mainParams"></param>
        /// <param name="mi"></param>
        /// <returns></returns>
        private static object[] GetParameters(List<object> mainParams, MethodInfo mi)
        {
            object[] parameters = null;

            if (mainParams != null && mainParams.Count > 0 && mainParams.Count == mi.GetParameters().Length)
            {
                parameters = mainParams.ToArray();
            }
            else if (mi.GetParameters().Length > 0)
            {
                parameters = new object[mi.GetParameters().Length];

                int i = 0;
                foreach (ParameterInfo parInfo in mi.GetParameters())
                {
                    if (parInfo.Name == "thisForm")
                    {
                        bool IsSet = false;

                        //Заменить на mainParams.Find
                        if (mainParams != null)
                            foreach (object o in mainParams)
                            {
                                if (o is MLCodeForm)
                                {
                                    parameters[i] = o;
                                    IsSet = true;
                                    break;
                                }
                            }

                        if (!IsSet)
                        {
                            parameters[i] = new MLCodeForm();
                        }
                    }
                    else if (parInfo.Name == "browser")
                    {
                        parameters[i] = new MLBrowser();
                    }

                    i++;
                }
            }
            return parameters;
        }

        #endregion
    }
}
